package ar.com.jsejtman.common.security.credential.entity;

import ar.com.jsejtman.common.security.schema.entity.AuthoritiesGroup;
import ar.com.jsejtman.common.security.schema.entity.Authority;
import ar.com.jsejtman.common.security.schema.entity.AuthorityOverride;
import ar.com.jsejtman.common.security.schema.entity.SecurityUser;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.UserDetails;

/**
 * Credencial de seguridad de un usuario para el sistema. Se basa en la credencial de seguridad de
 * Spring, pero agrega información adicional.
 * 
 * @author jsejtman
 */
public class SecurityCredential implements UserDetails {

	/**
	 * Prefijo de rol utilizado por Spring Security.
	 */
	private static final String ROLE_PREFIX = "ROLE_";

	/**
	 * Serial version UID.
	 */
	private static final long serialVersionUID = -4013797884685329798L;

	/**
	 * Nombre de usuario (no es el nombre y apellido, sino el usuario con el cual puede ingresar al
	 * sistema).
	 */
	private String username;

	/**
	 * Clave para ingreso al sistema.
	 */
	private String password;

	/**
	 * Primer (y segundo) nombre del usuario.
	 */
	private String firstName;

	/**
	 * Apellido del usuario.
	 */
	private String lastName;

	/**
	 * Dirección de correo electrónico del usuario (útil para recuperar una clave perdida, por
	 * ejemplo).
	 */
	private String emailAddress;

	/**
	 * Fecha de deshabilitación del usuario ("Null" si el usuario está habilitado).
	 */
	private Date disabledDate;

	/**
	 * Fecha a partir de la cual el usuario debe renovar su clave de acceso ("Null" si debe
	 * renovarla en el próximo acceso, sin importar la fecha).
	 */
	private Date credentialsExpirationDate;

	/**
	 * Lista de permisos del usuario.
	 */
	private Collection<GrantedAuthority> authorities;

	/**
	 * Construye una credencial de seguridad para un usuario de seguridad.
	 * 
	 * @param securityUser Usuario de seguridad.
	 */
	public SecurityCredential(final SecurityUser securityUser) {

		if (securityUser == null) {
			throw new IllegalArgumentException("El usuario de seguridad es un parámetro requerido "
			        + "para conformar una credencial de seguridad.");
		}

		this.username = securityUser.getUsername();
		this.password = securityUser.getPassword();
		this.firstName = securityUser.getFirstName();
		this.lastName = securityUser.getLastName();
		this.emailAddress = securityUser.getEmailAddress();
		this.disabledDate = securityUser.getDisabledDate();
		this.credentialsExpirationDate = securityUser.getCredentialsExpirationDate();

		this.authorities = new ArrayList<GrantedAuthority>();

		/* Agrega los permisos de los grupos (si tiene) */
		this.configureAuthoritiesForUserGroups(securityUser);

		/* Agrega o quita permisos según definiciones de sobreescritura (si tiene) */
		this.configureOverridesForUserAuthorities(securityUser);
	}

	/**
	 * Agrega las autoridades correspondientes a los grupos a los que pertenece el usuario de
	 * seguridad, a la lista de autoridades que gestionará la credencial de seguridad.
	 * 
	 * @param securityUser Usuario de seguridad del cual obtener los grupos de autoridades.
	 */
	private void configureAuthoritiesForUserGroups(final SecurityUser securityUser) {

		if (securityUser.getAllAuthorityGroups() != null) {
			for (AuthoritiesGroup group : securityUser.getAllAuthorityGroups()) {

				if (group.getAllAuthorities() != null) {
					for (Authority authority : group.getAllAuthorities()) {
						this.authorities.add(new GrantedAuthorityImpl(
						        SecurityCredential.ROLE_PREFIX + authority.getCode()));
					}
				}
			}
		}
	}

	/**
	 * Configura las sobreescrituras para las autoridades por defecto del usuario.
	 * 
	 * @param securityUser Usuario de seguridad del cual obtener las autoridades por defecto.
	 */
	private void configureOverridesForUserAuthorities(final SecurityUser securityUser) {

		if (securityUser.getAllAuthoritiesOverrides() != null) {
			for (AuthorityOverride override : securityUser.getAllAuthoritiesOverrides()) {

				final GrantedAuthority grantedAuthority =
				        new GrantedAuthorityImpl(SecurityCredential.ROLE_PREFIX
				                + override.getAuthority().getCode());

				if (override.isEnabled() && !this.authorities.contains(grantedAuthority)) {
					this.authorities.add(grantedAuthority);
				} else if (!override.isEnabled() && this.authorities.contains(grantedAuthority)) {
					this.authorities.remove(grantedAuthority);
				}
			}
		}
	}

	/**
	 * Devuelve el valor de username.
	 * 
	 * @return El valor de username.
	 */
	@Override
	public final String getUsername() {

		return (this.username);
	}

	/**
	 * Devuelve el valor de password.
	 * 
	 * @return El valor de password.
	 */
	@Override
	public final String getPassword() {

		return (this.password);
	}

	/**
	 * Devuelve el valor de firstName.
	 * 
	 * @return El valor de firstName.
	 */
	public final String getFirstName() {

		return (this.firstName);
	}

	/**
	 * Devuelve el valor de lastName.
	 * 
	 * @return El valor de lastName.
	 */
	public final String getLastName() {

		return (this.lastName);
	}

	/**
	 * Devuelve el valor de emailAddress.
	 * 
	 * @return El valor de emailAddress.
	 */
	public final String getEmailAddress() {

		return (this.emailAddress);
	}

	/**
	 * Devuelve el valor de disabledDate.
	 * 
	 * @return El valor de disabledDate.
	 */
	public final Date getDisabledDate() {

		return (this.disabledDate);
	}

	/**
	 * Devuelve el valor de credentialsExpirationDate.
	 * 
	 * @return El valor de credentialsExpirationDate.
	 */
	public final Date getCredentialsExpirationDate() {

		return (this.credentialsExpirationDate);
	}

	/**
	 * Devuelve el valor de authorities.
	 * 
	 * @return El valor de authorities.
	 */
	@Override
	public final Collection<GrantedAuthority> getAuthorities() {

		return (this.authorities);
	}

	@Override
	public final boolean isAccountNonExpired() {

		/* Este atributo no se utiliza */
		return (true);
	}

	@Override
	public final boolean isAccountNonLocked() {

		/* Este atributo no se utiliza */
		return (true);
	}

	@Override
	public final boolean isCredentialsNonExpired() {

		final Long now = new Date().getTime();

		return (this.credentialsExpirationDate == null || now < this.credentialsExpirationDate
		        .getTime());
	}

	@Override
	public final boolean isEnabled() {

		return (this.disabledDate == null);
	}
}
